<!doctype html>
<html lang="en-us">
<head>
	<meta name="generator" content="Hugo 0.63.2" />

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>刹宕博客 | 人如秋鸿来有信，事若春梦了无痕</title>
    <meta property="og:title" content="刹宕博客 | 人如秋鸿来有信，事若春梦了无痕">
    <meta property="og:type" content="website">
    <meta name="Keywords" content="java,系统架构,shutdown,孟江莱">
    <meta name="description" content="Java">
    <meta property="og:url" content="http://www.shutdown.cn/">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href='/css/normalize.css'>
    <link rel="stylesheet" href='/css/style.css'>
    <link rel="alternate" type="application/rss+xml+xml" href="http://www.shutdown.cn/index.xml" title="刹宕博客" />
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

    


    
    
</head>


<body>
    <header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <h1>
                        <a id="logo" href="http://www.shutdown.cn">
                            刹宕博客
                        </a>
                    </h1>
                
                <p class="description">人如秋鸿来有信，事若春梦了无痕</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    <a class="current" href="http://www.shutdown.cn">首页</a>
                    
                    <a  href="http://www.shutdown.cn/tools/" title="工具">工具</a>
                    
                    <a  href="http://www.shutdown.cn/archives/" title="归档">归档</a>
                    
                    <a  href="http://www.shutdown.cn/about/" title="关于">关于</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>

    <div id="body">
        <div class="container">
            <div class="col-group">

                <div class="col-8" id="main">
                    
<div class="res-cons">
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/redis-bgsave/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            bgsave配置 bgsave命令生成的rdb文件，配置在配置文件的 dir配置后的目录
默认文件名 ：/var/lib/redis/dump.rdb
conf]# cat redis-cluster-8002.conf daemonize no tcp-backlog 65536 timeout 0 tcp-keepalive 0 loglevel notice databases 16 dir /opt/cachecloud/data 如，上述配置，生成的dump文件就在 /opt/cachecloud/data目录中，而且是带端口的dump-8002.rdb
bgsave的工作流程 Redis的cow ，cow = copy on write Redis创建子进程以后，根本不进行数据的copy，主进程与子进程是共享数据的。主进程继续对外提供读写服务。 虽然不copy数据，但是kernel会把主进程中的所有内存页的权限都设为read-only，主进程和子进程访问数据的指针都指向同一内存地址。 主进程发生写操作时，因为权限已经设置为read-only了，所以会触发页异常中断（page-fault）。在中断处理中，需要被写入的内存页面会复制一份，复制出来的旧数据交给子进程使用，然后主进程该干啥就干啥。 也就是说，在进行IO操作写盘的过程中（on write），对于没有改变的数据，主进程和子进程资源共享；只有在出现了需要变更的数据时（写脏的数据），才进行copy操作。
 以上一段引用自 https://blog.csdn.net/zmflying8177/article/details/103500645
  这一块我有一个疑惑，在redis深度历险和另一篇文章里面 https://blog.csdn.net/wejack/article/details/122514049 里面，都写的是拷出来的文件，给主进程写数据用，而上面这一段，参考 https://blog.csdn.net/zmflying8177/article/details/103500645 ，写的是拷出来一份，给子进程用，主进程该进行写入，从最终的结果上来说，确实是让子进程读取拷过来的一份就好了，主进程写入最终什么都不用动，将为子进程拷出来的对象删掉就可以了，但是上面说的将主进程的内存页的权限设置为readonly了，那再修改这个readonly权限会不会有什么操作损耗，亦或者说，从readonly拷出来的内存页面也是readonly的，其实无论改哪个损耗是一样的，如果是这样的话， 从最终结果看，还是拷出来的给子进程用会更方便一些
 在最理想的情况下，也就是生成RDB文件的过程中，一直没有写操作的话，就根本不会发生内存的额外占用。
当然，仍然需要合理配置Linux的内存分配策略。避免在写操作过于集中时，发生因为物理内存不足导致fork失败的情况。……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/redis-bgsave/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/Redis%E5%86%85%E6%A0%B8%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%A1%86%E6%9E%B6/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            简介： 本文将对Redis内核单元测试框架进行基本的解析，并对如何编写测试用例进行基本的讲解。
在修改Redis内核之后，第一步我们需要做的就是添加或者对应的单元测试用例来进行基本的单元测试。本文将对Redis内核单元测试框架进行基本的解析，并对如何编写测试用例进行基本的讲解。
单元测试框架流程 Redis单元测试框架是基于tcl sh脚本实现的，其启动的方式为runtest [options]。 每一类的测试case写在单独的测试文件中，测试文件列表写入到test_server中all_tests列表中。 在启动测试时，会以server模式启动一个测试服务器，再启动多个测试客户端与之通信。由测试服务器会给空闲的测试服务端发送测试任务，参数为测试用例所在脚本文件名，由测试客户端执行对应的测试用例。详细的流程图如下：
1. processOptions 对选项进行解析，其中默认的模式是server模式，进入test_server_main函数; 若带了client选项则进入test_client_main函数.
2. test_server_main  内部维护了一系列当前的测试客户端状态列表; accept_test_clients 创建一个socket fd，侦听来自测试客户端的消息； 按照传入的参数，以runtest &ndash;client的方式启动n个测试client；  3. test_client_main 启动测试客户端，往测试服务器的fd上发送ready消息，开启客户端与服务端的交互流程;
4. 客户端与服务端的交互  客户端启动后，往测试服务器fd上发送ready消息; 服务端收到客户端ready或done事件后，检查所有的测试集，若还有测试任务未完成，则使用signal_idle_client方法往测试客户端发送测试任务，即&quot;run 测试用例脚本文件名&quot;消息； 客户端收到run消息后，调用execute_tests $data方法执行测试用例脚本文件; 客户端执行完测试case脚本后，往服务端发送done事件。再次进入第2步;  测试用例编写 1. 增加测试用例 新建一个测试用例文件，比如dummy.tcl,将之加入到test_helper.tcl的all_tests列表里
set ::all_tests { unit/auth ... unit/dummy ... } 这样启动测试的时候，会自动执行unit/dummy.tcl里面的测试用例;
2. 测试用例文件 每个测试用例文件里面可以包含多个start_server的部分，每个start_server都会启动一个redis实例。 每个start_server内部包含多个test函数模块，每个test函数对应一个测试用例。 例子：auth.tcl
start_server {tags {&quot;auth&quot;}} { test {AUTH fails if there is no password configured server side} { catch {r auth foo} err set _ $err } {ERR*no password*} } start_server {tags {&quot;auth&quot;} overrides {requirepass foobar}} { test {AUTH fails when a wrong password is given} { catch {r auth wrong!} err set _ $err } {ERR*invalid password} test {Arbitrary command gives an error when AUTH is required} { catch {r set foo bar} err set _ $err } {NOAUTH*} test {AUTH succeeds when the right password is given} { r auth foobar } {OK} test {Once AUTH succeeded we can actually send commands to the server} { r set foo 100 r incr foo } {101} } 3.……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/Redis%E5%86%85%E6%A0%B8%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E6%A1%86%E6%9E%B6/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/Redis%E5%AD%A6%E4%B9%A0%E6%96%87%E6%A1%A3/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            Redis学习文档 Redis安装和启动 安装Redis
1 2 3 4 5 6 7 8 9 10 11 12  mkdir -p /usr/redis cd /usr/redis # 下载redis源码包 wget http://download.redis.io/releases/redis-3.2.9.tar.gz tar -vxzf redis-3.2.9.tar.gz ln -s redis-3.2.9 redis cd redis # 直接make会报错误， zmalloc.h:50:31: 致命错误：jemalloc/jemalloc.h：没有那个文件或目录 # 加 MALLOC=libc 参数可以正常编译 make MALLOC=libc # 将运行文件安装到 /usr/local/bin 下，可以在任意目录下执行redis命令 make install   启动Redis
redis-server 配置文件名称.conf 客户端连接操作
# -h接ip -p接端口 redis-cli -h 127.0.0.1 -p 6379 # 或者直接执行命令 redis-cli -h 127.0.0.1 -p 6379 get hello Redis数据结构及实现 Redis的数据结构分别是： string（字符串） 、 hash（哈希） 、 list（列表） 、 set（集合） 、 zset（有序集 合）
使用type命令实际返回的就是当前键的数据结构类型
127.0.0.1:6379&gt; type hello string    数据结构 内部编码     string ·int： 8个字节的长整型。·embstr： 小于等于39个字节的字符串。·raw： 大于39个字节的字符串。   hash ·ziplist（压缩列表） ： 当哈希类型元素个数小于hash-max-ziplist-entries配置（默认512个） 、 同时所有值都小于hash-max-ziplist-value配置（默认64字节） 时， Redis会使用ziplist作为哈希的内部实现， ziplist使用更加紧凑的结构实现多个元素的连续存储， 所以在节省内存方面比hashtable更加优秀。·hashtable（哈希表） ： 当哈希类型无法满足ziplist的条件时， Redis会使用hashtable作为哈希的内部实现， 因为此时ziplist的读写效率会下降， 而hashtable的读写时间复杂度为O（1） 。   List ·ziplist（压缩列表） ： 当列表的元素个数小于list-max-ziplist-entries配置（默认512个） ， 同时列表中每个元素的值都小于list-max-ziplist-value配置时（默认64字节） ， Redis会选用ziplist来作为列表的内部实现来减少内存的使用。·linkedlist（链表） ： 当列表类型无法满足ziplist的条件时， Redis会使用linkedlist作为列表的内部实现。   set ·intset（整数集合） ： 当集合中的元素都是整数且元素个数小于set-maxintset-entries配置（默认512个） 时， Redis会选用intset来作为集合的内部实现， 从而减少内存的使用。·hashtable（哈希表） ： 当集合类型无法满足intset的条件时， Redis会使用hashtable作为集合的内部实现。   zset ·ziplist（压缩列表） ： 当有序集合的元素个数小于zset-max-ziplistentries配置（默认128个） ， 同时每个元素的值都小于zset-max-ziplist-value配置（默认64字节） 时， Redis会用ziplist来作为有序集合的内部实现， ziplist可以有效减少内存的使用。·skiplist（跳跃表） ： 当ziplist条件不满足时， 有序集合会使用skiplist作为内部实现， 因为此时ziplist的读写效率会下降。       数据结构 使用场景     string 缓存计数共享session   hash 对象信息存储   List 队列列表   set 标签推荐等   zset 排行榜    查看内部编码命令 object encoding……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/Redis%E5%AD%A6%E4%B9%A0%E6%96%87%E6%A1%A3/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/Springboot2.x-%E9%9B%86%E6%88%90-jedis%E5%92%8Cspring-boot-starter-data-redis%E7%9A%84%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%AF%94%E8%BE%83Jedis%E5%AE%8C%E8%83%9C%E9%99%84%E5%B8%A6%E6%BA%90%E7%A0%81/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            文章系转载，方便整理和归纳
版权声明：本文为CSDN博主「斌哥谈编程」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。 原文链接：https://blog.csdn.net/houpeibin2012/article/details/105839651
 1.pom文件引入jedis和spring-boot-starter-data-redis的依赖 2.其他依赖的引入（Swagger） 3.Jedis配置类 4.Jedis及spring-boot-starter-data-redis的配置信息 5.Swagger配置类 6.编写测试相关类 7.测试 7.1插入数据测试 7.2读取数据测试 7.3多线程读取数据测试 8. 结论
温馨提示： 本文配套代码：https://gitee.com/guduwuhen/springboot2-lesson/tree/master/redispro
1.pom文件引入jedis和spring-boot-starter-data-redis的依赖  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  &lt;properties&gt; &lt;java.version&gt;1.8&lt;/java.version&gt; &lt;jedis.version&gt;2.9.0&lt;/jedis.version&gt; &lt;fastjson.version&gt;1.2.68&lt;/fastjson.version&gt; &lt;guava.version&gt;28.2-jre&lt;/guava.version&gt; &lt;/properties&gt; &lt;dependency&gt; &lt;groupId&gt;redis.clients&lt;/groupId&gt; &lt;artifactId&gt;jedis&lt;/artifactId&gt; &lt;version&gt;${jedis.version}&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt; &lt;/dependency&gt;   Springboot 使用的是2.2.4.RELEASE版本。
2.其他依赖的引入（Swagger）  如下是完整的pom文件代码：
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101  &lt;?……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/Springboot2.x-%E9%9B%86%E6%88%90-jedis%E5%92%8Cspring-boot-starter-data-redis%E7%9A%84%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E6%AF%94%E8%BE%83Jedis%E5%AE%8C%E8%83%9C%E9%99%84%E5%B8%A6%E6%BA%90%E7%A0%81/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/spring%E8%8E%B7%E5%8F%96-maven-profile/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            https://www.cnblogs.com/zeng1994/p/06917ed3b98677fa7a1b0f74de16c3be.html
使用maven的profile功能，我们可以实现多环境配置文件的动态切换，可参考我的上一篇博客。但随着SpringBoot项目越来越火，越来越多人喜欢用SpringBoot的profile功能。但是用SpringBoot的profile功能时，一般我们默认激活的profile肯定是开发环境的profile。当我们打成jar包后，如果在生产环境下运行，就需要在运行这个jar包的命令后面加个命令行参数来指定切换的profile。虽然这样很方便，但是容易忘记加这个参数。
我们可以通过maven的profile功能和SpringBoot的profile功能结合使用。效果为：当maven打包时通过profile指定配置为test环境的配置，那么我们SpringBoot里面默认激活的就是test环境的配置。 这样我们只需要打包时指定profile后，直接运行jar就可以，不需要在命令行加参数了。这个效果就和我们普通web项目使用maven的profile的效果类似了。
一、思路 （1）通过maven的profile功能，在打包的时候，通过-P指定maven激活某个pofile，这个profile里面配置了一个参数activatedProperties，不同的profile里面的这个参数的值不同
（2）SpringBoot的application.properties文件里面spring.profiles.active填的值取上面maven的activatedProperties参数值。
这样能实现的效果为：
示例一： maven打包命令为 mvn clean package -P test 那么application.properties里面的spring.profiles.active值就是maven中 id为test的profile的activatedProperties参数值 示例二： maven打包命令为 mvn clean package -P product 那么application.properties里面的spring.profiles.active值就是maven中 id为product的profile的activatedProperties参数值 二、案例 （1）项目结构介绍 项目结构如下图所示，是个常见的SpringBoot项目结构，不同环境的propertis文件的后缀不同（见图中红框处）
（2）pom文件中配置maven的profile maven的profile的配置见下面代码
注意：maven的profile中activatedProperties参数值需要和SpringBoot的不同环境Properties文件的后缀一样。
比如开发环境的Properties的文件名为application-develop.properties，那么maven中develop的profile里面的activatedProperties参数值就应该是develop
 &lt;profiles&gt; &lt;profile&gt; &lt;!-- 开发 --&gt; &lt;id&gt;develop&lt;/id&gt; &lt;activation&gt; &lt;activeByDefault&gt;true&lt;/activeByDefault&gt; &lt;/activation&gt; &lt;properties&gt; &lt;activatedProperties&gt;develop&lt;/activatedProperties&gt; &lt;/properties&gt; &lt;/profile&gt; &lt;profile&gt; &lt;!-- 测试 --&gt; &lt;id&gt;fuy&lt;/id&gt; &lt;properties&gt; &lt;activatedProperties&gt;fuy&lt;/activatedProperties&gt; &lt;/properties&gt; &lt;/profile&gt; &lt;profile&gt; &lt;!-- 生产 --&gt; &lt;id&gt;production&lt;/id&gt; &lt;properties&gt; &lt;activatedProperties&gt;production&lt;/activatedProperties&gt; &lt;/properties&gt; &lt;/profile&gt; &lt;/profiles&gt; （3）application.properties中的配置 在application.properties文件中配置SpringBoot默认激活的propertis文件。这时候spring.profiles.active取上面maven的profile里面配置的activatedProperties的值，这个取值要用@符号来取。具体见下面代码
spring.profiles.active=${activatedProperties} （4）如何打包 打包时用 mvn clean package -P profile的id
​ 如果不加-P参数，那么默认就是true所在的profile
（5）效果图 ​ 当我们打包命令为mvn clean package -P production 时，解压后的jar包中application.properties配置文件中spring.profiles.active的值自动变成了production
​ 三、小结 （1）该方式优点：打包后不需要通过命令行参数来切换不同环境的配置文件，把指定环境的这一步放到了maven打包的命令上
（2）该方式其实是利用了maven的profile功能和SpringBoot的profile相结合使用
四、参考链接 （1）https://www.cnblogs.com/zeng1994/p/a442108012ffd6a97b22c63055b48fe9.html
（2）http://dolszewski.com/spring/spring-boot-properties-per-maven-profile/……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/spring%E8%8E%B7%E5%8F%96-maven-profile/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/sublime-text%E5%A2%9E%E5%8A%A0%E6%8F%92%E5%85%A5%E5%BD%93%E5%89%8D%E6%97%B6%E9%97%B4%E5%BF%AB%E6%8D%B7%E9%94%AE/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            sublime text增加插入当前时间快捷键 1、创建时间插件 Tools -&gt; developer -&gt; New Plugin&hellip;
2、插入如下代码，保存在 Packages\User\addCurrentTime.py　import sublime import sublime_plugin import datetime class AddCurrentTimeCommand(sublime_plugin.TextCommand): def run(self, edit): self.view.run_command(&quot;insert_snippet&quot;, { &quot;contents&quot;: &quot;%s&quot; % datetime.datetime.now().strftime(&quot;%Y-%m-%d %H:%M:%S&quot;) } ) 3、设置快捷键映射 Preference → Key Bindings - User
{ &quot;keys&quot;: [&quot;ctrl+t&quot;], &quot;command&quot;: &quot;add_current_time&quot;} 4、重启sublime text即可……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/sublime-text%E5%A2%9E%E5%8A%A0%E6%8F%92%E5%85%A5%E5%BD%93%E5%89%8D%E6%97%B6%E9%97%B4%E5%BF%AB%E6%8D%B7%E9%94%AE/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/sudo-%E4%B8%8E-su-%E4%B8%A4%E4%B8%AA%E5%91%BD%E4%BB%A4%E7%9A%84%E5%8C%BA%E5%88%AB/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            sudo 与 su 两个命令的最大区别是：
sudo 命令需要输入当前用户的密码，su 命令需要输入 root 用户的密码。另外一个区别是其默认行为。sudo 命令只允许使用提升的权限运行单个命令，而 su 命令会启动一个新的 shell，同时允许使用 root 权限运行尽可能多的命令，直到明确退出登录。
su 用以切换成不同的用户的身份
默认只是切换身份，并没有切换环境变量，环境变量依然是普通用户的。切换用户身份时，用户的环境变量也切换成新用户的环境变量,所以&rdquo;-&ldquo;不能省略,不然有些操作无法执行。
su root 输入root密码后切换之root用户但是pwd目录不变
su - root 输入root密码后切换之root用户但是pwd目录/root
sudo 一般加的是命令
sudo -i root与sudo - root、sudo -i ,sudo - ，sudo root效果相同 提示输入密码时该密码为当前账户的密码 要求执行该命令的用户必须在sudoers中才可以 su需要的是切换后账户的密 用法为“su 账户名称”
sudo : 暂时切换到超级用户模式以执行超级用户权限，一般指的是root用户，提示输入密码时该密码为当前用户的密码，而不是超级账户的密码。不过有时间限制，Ubuntu默认为一次时长15分钟。
su ： 切换到某某用户模式，提示输入密码时该密码为切换后账户的密码，用法为“su 账户名称”。如果后面不加账户时系统默认为root账户，密码也为超级账户的密码。没有时间限制。
sudo -i: 为了频繁的执行某些只有超级用户才能执行的权限，而不用每次输入密码，可以使用该命令。提示输入密码时该密码为当前账户的密码。没有时间限制。执行该命令后提示符变为“#”而不是“$”。想退回普通账户时可以执行“exit”或“logout” 。 要求执行该命令的用户必须在sudoers中才可以
sudo -i 直接运行sudo命令加-i参数 要求执行该命令的用户必须在sudoers中才可以
sudo su 运行sudo命令给su命令提权，运行su命令。 要求执行该命令的用户必须在sudoers中才可以。……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/sudo-%E4%B8%8E-su-%E4%B8%A4%E4%B8%AA%E5%91%BD%E4%BB%A4%E7%9A%84%E5%8C%BA%E5%88%AB/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/swap%E6%98%AF%E5%B9%B2%E5%98%9B%E7%9A%84/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            swap是干嘛的？ 在Linux下，SWAP的作用类似Windows系统下的“虚拟内存”。当物理内存不足时，拿出部分硬盘空间当SWAP分区（虚拟成内存）使用，从而解决内存容量不足的情况。
SWAP意思是交换，顾名思义，当某进程向OS请求内存发现不足时，OS会把内存中暂时不用的数据交换出去，放在SWAP分区中，这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时，又会把SWAP分区中的数据交换回物理内存中，这个过程称为SWAP IN。
当然，swap大小是有上限的，一旦swap使用完，操作系统会触发OOM-Killer机制，把消耗内存最多的进程kill掉以释放内存。
数据库系统为什么嫌弃swap？ 显然，swap机制的初衷是为了缓解物理内存用尽而选择直接粗暴OOM进程的尴尬。但坦白讲，几乎所有数据库对swap都不怎么待见，无论MySQL、Oracal、MongoDB抑或HBase，为什么？这主要和下面两个方面有关：
\1. 数据库系统一般都对响应延迟比较敏感，如果使用swap代替内存，数据库服务性能必然不可接受。对于响应延迟极其敏感的系统来讲，延迟太大和服务不可用没有任何区别，比服务不可用更严重的是，swap场景下进程就是不死，这就意味着系统一直不可用……再想想如果不使用swap直接oom，是不是一种更好的选择，这样很多高可用系统直接会主从切换掉，用户基本无感知。
\2. 另外对于诸如HBase这类分布式系统来说，其实并不担心某个节点宕掉，而恰恰担心某个节点夯住。一个节点宕掉，最多就是小部分请求短暂不可用，重试即可恢复。但是一个节点夯住会将所有分布式请求都夯住，服务器端线程资源被占用不放，导致整个集群请求阻塞，甚至集群被拖垮。
从这两个角度考虑，所有数据库都不喜欢swap还是很有道理的！
swap的工作机制 既然数据库们对swap不待见，那是不是就要使用swapoff命令关闭磁盘缓存特性呢？非也，大家可以想想，关闭磁盘缓存意味着什么？实际生产环境没有一个系统会如此激进，要知道这个世界永远不是非0即1的，大家都会或多或少选择走在中间，不过有些偏向0，有些偏向1而已。很显然，在swap这个问题上，数据库必然选择偏向尽量少用。HBase官方文档的几点要求实际上就是落实这个方针：尽可能降低swap影响。知己知彼才能百战不殆，要降低swap影响就必须弄清楚Linux内存回收是怎么工作的，这样才能不遗漏任何可能的疑点。
先来看看swap是如何触发的？ 简单来说，Linux会在两种场景下触发内存回收，一种是在内存分配时发现没有足够空闲内存时会立刻触发内存回收；一种是开启了一个守护进程（swapd进程）周期性对系统内存进行检查，在可用内存降低到特定阈值之后主动触发内存回收。第一种场景没什么可说，来重点聊聊第二种场景，如下图所示：
这里就要引出我们关注的第一个参数：vm.min_free_kbytes，代表系统所保留空闲内存的最低限watermark[min]，并且影响watermark[low]和watermark[high]。简单可以认为：
watermark[min] = min_free_kbytes watermark[low] = watermark[min] * 5 / 4 = min_free_kbytes * 5 / 4 watermark[high] = watermark[min] * 3 / 2 = min_free_kbytes * 3 / 2 watermark[high] - watermark[low] = watermark[low] - watermark[min] = min_free_kbytes / 4 可见，LInux的这几个水位线与参数min_free_kbytes密不可分。min_free_kbytes对于系统的重要性不言而喻，既不能太大，也不能太小。
min_free_kbytes如果太小，［min,low］之间水位的buffer就会很小，在kswapd回收的过程中一旦上层申请内存的速度太快（典型应用：数据库），就会导致空闲内存极易降至watermark[min]以下，此时内核就会进行direct reclaim（直接回收），直接在应用程序的进程上下文中进行回收，再用回收上来的空闲页满足内存申请，因此实际会阻塞应用程序，带来一定的响应延迟。当然，min_free_kbytes也不宜太大，太大一方面会导致应用程序进程内存减少，浪费系统内存资源，另一方面还会导致kswapd进程花费大量时间进行内存回收。再看看这个过程，是不是和Java垃圾回收机制中CMS算法中老生代回收触发机制神似，想想参数-XX:CMSInitiatingOccupancyFraction，是不是？官方文档中要求min_free_kbytes不能小于1G（在大内存系统中设置8G），就是不要轻易触发直接回收。
至此，基本解释了Linux的内存回收触发机制以及我们关注的第一个参数vm.min_free_kbytes。接下来简单看看Linux内存回收都回收些什么。Linux内存回收对象主要分为两种：
\1. 文件缓存，这个容易理解，为了避免文件数据每次都要从硬盘读取，系统会将热点数据存储在内存中，提高性能。如果仅仅将文件读出来，内存回收只需要释放这部分内存即可，下次再次读取该文件数据直接从硬盘中读取即可（类似HBase文件缓存）。那如果不仅将文件读出来，而且对这些缓存的文件数据进行了修改（脏数据），回收内存就需要将这部分数据文件写会硬盘再释放（类似MySQL文件缓存）。
\2. 匿名内存，这部分内存没有实际载体，不像文件缓存有硬盘文件这样一个载体，比如典型的堆、栈数据等。这部分内存在回收的时候不能直接释放或者写回类似文件的媒介中，这才搞出来swap这个机制，将这类内存换出到硬盘中，需要的时候再加载出来。
具体Linux使用什么算法来确认哪些文件缓存或者匿名内存需要被回收掉，这里并不关心，有兴趣可以参考这里。但是有个问题需要我们思考：既然有两类内存可以被回收，那么在这两类内存都可以被回收的情况下，Linux到底是如何决定到底是回收哪类内存呢？还是两者都会被回收？这里就牵出来了我们第二个关心的参数：swappiness，这个值用来定义内核使用swap的积极程度，值越高，内核就会积极地使用swap，值越低，就会降低对swap的使用积极性。该值取值范围在0～100，默认是60。这个swappiness到底是怎么实现的呢？具体原理很复杂，简单来讲，swappiness通过控制内存回收时，回收的匿名页更多一些还是回收的文件缓存更多一些来达到这个效果。swappiness等于100，表示匿名内存和文件缓存将用同样的优先级进行回收，默认60表示文件缓存会优先被回收掉，至于为什么文件缓存要被优先回收掉，大家不妨想想（回收文件缓存通常情况下不会引起IO操作，对系统性能影响较小）。对于数据库来讲，swap是尽量需要避免的，所以需要将其设置为0。此处需要注意，设置为0并不代表不执行swap哦！
至此，我们从Linux内存回收触发机制、Linux内存回收对象一直聊到swap，将参数min_free_kbytes以及swappiness进行了解释。接下来看看另一个与swap有关系的参数：zone_reclaim_mode，文档说了设置这个参数为0可以关闭NUMA的zone reclaim，这又是怎么回事？提起NUMA，数据库们又都不高兴了，很多DBA都曾经被坑惨过。那这里简单说明三个小问题：NUMA是什么？NUMA和swap有什么关系？zone_reclaim_mode的具体意义？
NUMA（Non-Uniform Memory Access）是相对UMA来说的，两者都是CPU的设计架构，早期CPU设计为UMA结构，如下图(图片来自网络)所示：
为了缓解多核CPU读取同一块内存所遇到的通道瓶颈问题，芯片工程师又设计了NUMA结构，如下图（图片来自网络）所示：
这种架构可以很好解决UMA的问题，即不同CPU有专属内存区，为了实现CPU之间的”内存隔离”，还需要软件层面两点支持：
\1. 内存分配需要在请求线程当前所处CPU的专属内存区域进行分配。如果分配到其他CPU专属内存区，势必隔离性会受到一定影响，并且跨越总线的内存访问性能必然会有一定程度降低。
\2. 另外，一旦local内存（专属内存）不够用，优先淘汰local内存中的内存页，而不是去查看远程内存区是否会有空闲内存借用。
这样实现，隔离性确实好了，但问题也来了：NUMA这种特性可能会导致CPU内存使用不均衡，部分CPU专属内存不够使用，频繁需要回收，进而可能发生大量swap，系统响应延迟会严重抖动。而与此同时其他部分CPU专属内存可能都很空闲。这就会产生一种怪现象：使用free命令查看当前系统还有部分空闲物理内存，系统却不断发生swap，导致某些应用性能急剧下降。见叶金荣老师的MySQL案例分析：《找到MySQL服务器发生SWAP罪魁祸首》。
所以，对于小内存应用来讲，NUMA所带来的这种问题并不突出，相反，local内存所带来的性能提升相当可观。但是对于数据库这类内存大户来说，NUMA默认策略所带来的稳定性隐患是不可接受的。因此数据库们都强烈要求对NUMA的默认策略进行改进，有两个方面可以进行改进：
\1. 将内存分配策略由默认的亲和模式改为interleave模式，即会将内存page打散分配到不同的CPU zone中。通过这种方式解决内存可能分布不均的问题，一定程度上缓解上述案例中的诡异问题。对于MongoDB来说，在启动的时候就会提示使用interleave内存分配策略：
WARNING: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl –interleave=all mongod [other options] \2. 改进内存回收策略：此处终于请出今天的第三个主角参数zone_reclaim_mode，这个参数定义了NUMA架构下不同的内存回收策略，可以取值0/1/3/4，其中0表示在local内存不够用的情况下可以去其他的内存区域分配内存；1表示在local内存不够用的情况下本地先回收再分配；3表示本地回收尽可能先回收文件缓存对象；4表示本地回收优先使用swap回收匿名内存。可见，HBase推荐配置zone_reclaim_mode＝0一定程度上降低了swap发生的概率。
不都是swap的事 至此，我们探讨了三个与swap相关的系统参数，并且围绕Linux系统内存分配、swap以及NUMA等知识点对这三个参数进行了深入解读。除此之外，对于数据库系统来说，还有两个非常重要的参数需要特别关注：
\1. IO调度策略：这个话题网上有很多解释，在此并不打算详述，只给出结果。通常对于sata盘的OLTP数据库来说，deadline算法调度策略是最优的选择。
\2. THP（transparent huge pages）特性关闭。THP特性笔者曾经疑惑过很久，主要疑惑点有两点，其一是THP和HugePage是不是一回事，其二是HBase为什么要求关闭THP。经过前前后后多次查阅相关文档，终于找到一些蛛丝马迹。这里分四个小点来解释THP特性：
（1）什么是HugePage？
网上对HugePage的解释有很多，大家可以检索阅读。简单来说，计算机内存是通过表映射（内存索引表）的方式进行内存寻址，目前系统内存以4KB为一个页，作为内存寻址的最小单元。随着内存不断增大，内存索引表的大小将会不断增大。一台256G内存的机器,如果使用4KB小页, 仅索引表大小就要4G左右。要知道这个索引表是必须装在内存的，而且是在CPU内存，太大就会发生大量miss，内存寻址性能就会下降。
HugePage就是为了解决这个问题，HugePage使用2MB大小的大页代替传统小页来管理内存，这样内存索引表大小就可以控制的很小，进而全部装在CPU内存，防止出现miss。
（2）什么是THP（Transparent Huge Pages）?
HugePage是一种大页理论，那具体怎么使用HugePage特性呢？目前系统提供了两种使用方式，其一称为Static Huge Pages，另一种就是Transparent Huge Pages。前者根据名称就可以知道是一种静态管理策略，需要用户自己根据系统内存大小手动配置大页个数，这样在系统启动的时候就会生成对应个数的大页，后续将不再改变。而Transparent Huge Pages是一种动态管理策略，它会在运行期动态分配大页给应用，并对这些大页进行管理，对用户来说完全透明，不需要进行任何配置。另外，目前THP只针对匿名内存区域。
（3）HBase（数据库）为什么要求关闭THP特性？
THP是一种动态管理策略，会在运行期分配管理大页，因此会有一定程度的分配延时，这对追求响应延时的数据库系统来说不可接受。除此之外，THP还有很多其他弊端，可以参考这篇文章《why-tokudb-hates-transparent-hugepages》
（4）THP关闭/开启对HBase读写性能影响有多大？
为了验证THP开启关闭对HBase性能的影响到底有多大，本人在测试环境做了一个简单的测试：测试集群仅一个RegionServer，测试负载为读写比1:1。THP在部分系统中为always以及never两个选项，在部分系统中多了一个称为madvise的选项。可以使用命令 echo never/always &gt; /sys/kernel/mm/transparent_hugepage/enabled 来关闭/开启THP。测试结果如下图所示：
如上图，TPH关闭场景下（never）HBase性能最优，比较稳定。而THP开启的场景（always），性能相比关闭的场景有30%左右的下降，而且曲线抖动很大。可见，HBase线上切记要关闭THP。……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/swap%E6%98%AF%E5%B9%B2%E5%98%9B%E7%9A%84/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/VSCODE-markdown-%E5%AE%9E%E6%97%B6%E9%A2%84%E8%A7%88/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            VSCODE markdown 实时预览 1.首先不要安装markdown插件,这个应该是vscode内置了，我之前装了Markdown All in One，但是卸载了发现也能正常预览。
2.在markdown文档内容处右键，打开侧边预览。
或者
按命令快捷键 F1，搜索 Markdown:MPE:打开侧边预览 ，或者打开侧边预览就可以正常打开一个侧边内容区进行预览了，我之前因为装了Markdown All in One 所以搜索结果非常受干扰。……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/VSCODE-markdown-%E5%AE%9E%E6%97%B6%E9%A2%84%E8%A7%88/">阅读全文</a></p>
    </article>
    
    <article class="post">
        <header>
            <h1 class="post-title">
                <a href="http://www.shutdown.cn/post/%E4%B8%8B%E8%BD%BDJtopo-demo%E4%BB%A3%E7%A0%81/" title=""></a>
            </h1>
        </header>
        <date class="post-meta meta-date">
            1年1月1日
        </date>
        
        <div class="post-content">
            下载Jtopo
工作需要画拓扑图，看到jtopo官网的代码，http://jtopo.com/demo/statictis.html，想把demo下载下来做测试，但是官网展示的代码只给了js的代码。下面介绍下我如何将demo整个拷下来的。
以我要研究的demo页面为例，http://jtopo.com/demo/statictis.html，打开F12后刷新下页面，打开网络/network选项卡，点击statictis.html记录，将响应中的内容保存到html中。
将网络中出现的js ,css ,图片文件分别保存到相应的目录中，如下图中画红框的文件。
最终文件目录结构如下图。
demo运行参考文档 https://blog.csdn.net/wejack/article/details/122734253
打包好的源码资源 https://download.csdn.net/download/wejack/78274589……
        </div>
        <p class="readmore"><a href="http://www.shutdown.cn/post/%E4%B8%8B%E8%BD%BDJtopo-demo%E4%BB%A3%E7%A0%81/">阅读全文</a></p>
    </article>
    
    



<ol class="page-navigator">
    
    <li class="prev">
        <a href="http://www.shutdown.cn/page/3/">上一页</a>
    </li>
    

    
    <li >
        <a href="http://www.shutdown.cn/">1</a>
    </li>
    
    <li >
        <a href="http://www.shutdown.cn/page/2/">2</a>
    </li>
    
    <li >
        <a href="http://www.shutdown.cn/page/3/">3</a>
    </li>
    
    <li  class="current">
        <a href="http://www.shutdown.cn/page/4/">4</a>
    </li>
    
    <li >
        <a href="http://www.shutdown.cn/page/5/">5</a>
    </li>
    

    
    <li class="next">
        <a href="http://www.shutdown.cn/page/5/">下一页</a>
    </li>
    
</ol>



</div>

                </div>

                <div id="secondary">
    <section class="widget">
        <form id="search" action='http://www.shutdown.cn/search/' method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="http://www.shutdown.cn">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>
    
    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="http://www.shutdown.cn/post/lvs_keepalive_jenkins2_ha/" title="lvs&#43;keepalive配置Jenkins2高可用 ">lvs&#43;keepalive配置Jenkins2高可用 </a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/markdown/" title="markdown语法">markdown语法</a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/first/" title="hello world">hello world</a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/apt-get-update%E5%87%BA%E7%8E%B0NO_PUBKEY%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/" title=""></a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/BigKey/" title=""></a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/bigkey%E5%88%A0%E9%99%A4%E9%97%AE%E9%A2%98/" title=""></a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/cachecloud%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/" title=""></a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/copy-on-write/" title=""></a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/Cpu%E6%8C%96%E7%9F%BFRedis%E7%AB%9F%E6%98%AF%E5%86%85%E9%AC%BC/" title=""></a>
    </li>
    
    <li>
        <a href="http://www.shutdown.cn/post/docker-%E5%90%AF%E5%8A%A8-zookeeper/" title=""></a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">分类</h3>
<ul class="widget-list">
    
    <li><a href="http://www.shutdown.cn/categories/cicd/">cicd (1)</a></li>
    
    <li><a href="http://www.shutdown.cn/categories/%E5%88%86%E7%B1%BB/">分类 (2)</a></li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title">标签</h3>
<div class="tagcloud">
    
</div>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="http://www.shutdown.cn/index.xml">文章 RSS</a></li>
        </ul>
    </section>
</div>
            </div>
        </div>
    </div>
    <footer id="footer">
    <div class="container">
        &copy; 2013-2022版权所有：<a href="http://www.beian.miit.gov.cn">京ICP备13019191号-1</a> <a href="http://www.shutdown.cn">刹宕博客 By 孟江莱</a>.
        Powered by <a rel="nofollow noreferer noopener" href="https://gohugo.io" target="_blank">Hugo</a>.
        <a href="https://www.flysnow.org/" target="_blank">Theme</a> based on <a href="https://github.com/flysnow-org/maupassant-hugo" target="_blank">maupassant</a>.
    </div>
</footer>



<a id="rocket" href="#top"></a>
<script type="text/javascript" src='/js/totop.js?v=0.0.0' async=""></script>






</body>

</html>